/**
 *拼图游戏的数据结构，用于描述拼图游戏的节点状态和节点操作
 */
public class jigsawNode {
    private static final int dimension = 5;  // 拼图的维数 5*5
    private int[] nodesState;                // 拼图状态：第一位存储空白格的位置；其他各存储对应格子中的数值。
    private int nodeDepth;                   // 从初始状态到达此状态的步数
    private jigsawNode parent;               // 到达此节点的上一个节点
    private int estimatedValue;              // 代价估计值

    /**
     *创建一个新节点
     * @param data -> 一个N*N+1的一维数组,第1位代表空白格所处位置，其余N*N位分别代表每一格中所放方块的数值（顺序为从行到列）。
     */
    public jigsawNode(int[] data) {
        if(data.length == dimension*dimension+1) {
            this.nodesState = new int[data.length];
            System.arraycopy(data, 0, this.nodesState, 0, this.nodesState.length);
            this.nodeDepth = 0;
            this.parent = null;
            this.estimatedValue = 0;
        } else
            System.out.println("传入参数错误：当前的节点维数为5.请传入长度为" + (dimension * dimension + 1)
                    + "的节点状态数组，或者调整Jigsaw类中的节点维数dimension");
    }

    /**
     * 创建一个与输入参数相同的节点
     * @param jNode -> 用于复制
     */
    public jigsawNode(jigsawNode jNode) {
        this.nodesState = new int[dimension * dimension + 1];
        this.nodesState = jNode.nodesState.clone();
        this.nodeDepth = jNode.nodeDepth;
        this.parent = jNode.parent;
        this.estimatedValue = jNode.estimatedValue;
    }

    public static int getDimension() {
        return dimension;
    }

    public int[] getNodesState() {
        return nodesState;
    }

    public int getNodeDepth() {
        return nodeDepth;
    }

    public jigsawNode getParent() {
        return parent;
    }

    public int getEstimatedValue() {
        return estimatedValue;
    }

    public void setEstimatedValue(int estimatedValue) {
        this.estimatedValue = estimatedValue;
    }

    public void init() {
        this.estimatedValue = 0;
        this.nodeDepth = 0;
        this.parent = null;
    }

    /**
     * 用于检测一个节点是否为目标节点
     * @param obj -> 与当前节点进行比较
     */
    public boolean equals(Object obj) {
        for (int i = 0; i < this.nodesState.length; i++) {
            if (this.nodesState[i] != ((jigsawNode) obj).nodesState[i]) {
                return false;
            }
        }
        return true;
    }

    public String toString() {
        StringBuilder str = new StringBuilder();
        str.append("{").append(this.nodesState[0]);
        for(int i = 1; i <= dimension * dimension; i++)
            str.append(",").append(this.nodesState[i]);
        str.append("}");
        return str.toString();
    }

    /**
     * @return 当前拼图状态的字符串文本（矩阵形式）
     */
    public String toMatrixString() {
        StringBuilder str = new StringBuilder();
        for (int x = 1,index = 1; x <= dimension; x++) {
            for (int y = 1; y <= dimension; y++,index++){
                str.append(this.nodesState[index]).append("  ");
            }
            str.append("\n");
        }
        return str.toString();
    }

    /**
     * 探测当前状态中空白格的可移动方位
     * @return 返回一个一维数组，1到4位分别代表空白格是否能向上、下、左、右移动。 值为1时表示该方向可移动，值为0时表示该方向不可移动。
     */
    public int[] canMove() {
        int[] move = new int[] { 0, 0, 0, 0 };
        if (this.nodesState[0] > dimension && this.nodesState[0] <= dimension * dimension)
            move[0] = 1;  // 空白格可向上移
        if (this.nodesState[0] >= 1 && this.nodesState[0] <= dimension * (dimension - 1))
            move[1] = 1;  // 空白格可向下移
        if (this.nodesState[0] % dimension != 1)
            move[2] = 1;  // 空白格可向左移
        if (this.nodesState[0] % dimension != 0)
            move[3] = 1;  // 空白格可向右移
        return move;
    }

    /**
     * 空白格的移动
     * @param direction -> 0, 1, 2, 3分别表示上、下、左、右
     */
    public boolean move(int direction) {
        return switch (direction) {
            case 0 -> this.moveUp();
            case 1 -> this.moveDown();
            case 2 -> this.moveLeft();
            case 3 -> this.moveRight();
            default -> false;
        };
    }

    public boolean moveUp() {
        int emptyPos = this.nodesState[0];
        int emptyValue = this.nodesState[emptyPos];
        if (this.nodesState[0] > dimension && this.nodesState[0] <= dimension * dimension) {
            this.parent = new jigsawNode(this);
            this.nodeDepth++;
            this.nodesState[emptyPos] = this.nodesState[emptyPos - dimension];
            this.nodesState[emptyPos - dimension] = emptyValue;
            this.nodesState[0] = emptyPos - dimension;
            return true;
        }
        return false;
    }

    public boolean moveDown() {
        int emptyPos = this.nodesState[0];
        int emptyValue = this.nodesState[emptyPos];
        if (this.nodesState[0] >= 1 && this.nodesState[0] <= dimension * (dimension - 1)) {
            this.parent = new jigsawNode(this);
            this.nodeDepth++;
            this.nodesState[emptyPos] = this.nodesState[emptyPos + dimension];
            this.nodesState[emptyPos + dimension] = emptyValue;
            this.nodesState[0] = emptyPos + dimension;
            return true;
        }
        return false;
    }

    public boolean moveLeft() {
        int emptyPos = this.nodesState[0];
        int emptyValue = this.nodesState[emptyPos];
        if (this.nodesState[0] % dimension != 1) {
            this.parent = new jigsawNode(this);
            this.nodeDepth++;
            this.nodesState[emptyPos] = this.nodesState[emptyPos - 1];
            this.nodesState[emptyPos - 1] = emptyValue;
            this.nodesState[0] = emptyPos - 1;
            return true;
        }
        return false;
    }

    public boolean moveRight() {
        int emptyPos = this.nodesState[0];
        int emptyValue = this.nodesState[emptyPos];
        if (this.nodesState[0] % dimension != 0) {
            this.parent = new jigsawNode(this);
            this.nodeDepth++;
            this.nodesState[emptyPos] = this.nodesState[emptyPos + 1];
            this.nodesState[emptyPos + 1] = emptyValue;
            this.nodesState[0] = emptyPos + 1;
            return true;
        }
        return false;
    }
}